{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "fe12c203-e6a6-452c-a655-afb8a03a4ff5",
   "metadata": {},
   "source": [
    "# End of week 1 exercise\n",
    "Computer Engineering Tutor. User can ask the question and it answer assuming the user has some engineering background.\n",
    "\n",
    "## Sample computer-science questions (copy into the prompt)\n",
    "\n",
    "Try one of these example questions to get started. They are tailored to computer science and software engineering topics:\n",
    "\n",
    "- Explain Big O notation and give examples for common algorithms (e.g., sorting, searching).\n",
    "- How does a hash table handle collisions and what are common collision-resolution strategies?\n",
    "- What's the difference between processes and threads, and when should you use each?\n",
    "- How does TCP differ from UDP, and in which scenarios would you choose one over the other?\n",
    "- Explain ACID vs BASE in databases and when each consistency model is appropriate.\n",
    "- What is a race condition and how can you prevent it in concurrent programs?\n",
    "- How does an LRU cache work and where would you use it?\n",
    "- Describe the compilation pipeline from source code to machine code (lexing, parsing, optimization, codegen).\n",
    "\n",
    "## Setup & requirements\n",
    "\n",
    "This notebook demonstrates using both the OpenAI API and a local Ollama server to answer technical questions. Before running the notebook, make sure you complete the following setup steps.\n",
    "\n",
    "1) Python environment and dependencies\n",
    "\n",
    " - Create and activate a virtual environment (recommended).\n",
    " - Install the project dependencies:\n",
    "```bash\n",
    "pip install -r requirements.txt\n",
    "```\n",
    "\n",
    "2) Create a `.env` file\n",
    "\n",
    " - In the repository root create a file named `.env` and add your OpenAI API key:\n",
    "```text\n",
    "OPENAI_API_KEY=sk-<your-openai-key>\n",
    "```\n",
    " - Optionally you can add an Ollama API key variable if you use one (not required for the default local Ollama setup):\n",
    "```text\n",
    "OLLAMA_API_KEY=ollama\n",
    "```\n",
    " - This notebook reads `OPENAI_API_KEY` from the environment. If you choose to use `OLLAMA_API_KEY`, adjust the client instantiation accordingly.\n",
    "\n",
    "3) Install and run Ollama (local LLM server)\n",
    "\n",
    " - Install Ollama following their official instructions for your OS (macOS, Linux, Windows).\n",
    " - Start the Ollama server so it is reachable on `localhost:11434`. For many installations the server listens on port `11434` by default.\n",
    "   - Example (if using the Ollama CLI): run the Ollama service so the API is available at `http://localhost:11434`.\n",
    " - If you change the port or host, update the `base_url` used for the Ollama client in the notebook.\n",
    "\n",
    "4) Quick verification (once Ollama is running)\n",
    "\n",
    " - Check the daemon is reachable:\n",
    "```bash\n",
    "curl -I http://localhost:11434/\n",
    "```\n",
    " - List available models (use the exact model id reported here in the notebook):\n",
    "```bash\n",
    "curl http://localhost:11434/v1/models | jq .\n",
    "```\n",
    " - Example model id you may see: `llama3.2:1b`. Use that exact id when calling the model.\n",
    "\n",
    "5) Notebook client configuration notes\n",
    "\n",
    " - The notebook uses the OpenAI-compatible Python client. For Ollama the notebook sets the client like:\n",
    "```python\n",
    "ollama = OpenAI(base_url=\"http://localhost:11434/v1\", api_key=\"ollama\")\n",
    "```\n",
    " - Important: For the OpenAI client installed in this environment the `base_url` must include the `/v1` suffix so requests go to `http://localhost:11434/v1/chat/completions`. If you get a 404 error, check whether the client is calling a path without `/v1` and adjust `base_url` accordingly.\n",
    "\n",
    "6) Running the notebook\n",
    "\n",
    " - After completing steps above, run the cells from top to bottom.\n",
    " - If you get errors, first try non-streaming calls to check basic connectivity:\n",
    "```python\n",
    "ask_llm(ollama, \"Say hi\", MODEL_LLAMA, stream=False)\n",
    "```\n",
    " - If non-streaming works but streaming fails, the streaming loop may need to be adjusted to match the chunk format returned by your OpenAI client version; the notebook contains defensive streaming handling to help with this.\n",
    "\n",
    "## Where to find diagnostics\n",
    "\n",
    " - A diagnostic code cell follows the environment setup; run it to print the OpenAI package version, the Ollama `/v1/models` response, and a quick non-streaming test reply.\n",
    "\n",
    "Troubleshooting tips\n",
    "\n",
    " - `Connection refused` or no response: Ollama daemon is not running or wrong port.\n",
    " - `404 page not found`: incorrect base_url or missing `/v1` in requests.\n",
    " - `model not found`: use the exact model id reported by `/v1/models` (e.g., `llama3.2:1b`).\n",
    " - `401/403`: authentication mismatch — either remove the auth header for a local server that doesn't require it or set the correct token."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c1070317-3ed9-4659-abe3-828943230e03",
   "metadata": {},
   "outputs": [],
   "source": [
    "# imports\n",
    "# If these fail, please check you're running from an 'activated' environment with (llms) in the command prompt\n",
    "\n",
    "import os\n",
    "from dotenv import load_dotenv\n",
    "from IPython.display import Markdown, display, update_display\n",
    "from openai import OpenAI"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4a456906-915a-4bfd-bb9d-57e505c5093f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# constants\n",
    "\n",
    "MODEL_GPT = 'gpt-4o-mini'\n",
    "MODEL_LLAMA = 'llama3.2:1b'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a8d7923c-5f28-4c30-8556-342d7c8497c1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# set up environment\n",
    "load_dotenv(override=True)\n",
    "api_key = os.getenv('OPENAI_API_KEY')\n",
    "\n",
    "if api_key and api_key.startswith('sk-proj-') and len(api_key)>10:\n",
    "    print(\"API key looks good so far\")\n",
    "else:\n",
    "    print(\"There might be a problem with your API key? Please visit the troubleshooting notebook!\")\n",
    "    \n",
    "\n",
    "openai = OpenAI()\n",
    "ollama = OpenAI(base_url=\"http://localhost:11434/v1\" , api_key=\"ollama\")\n",
    "\n",
    "# set up system prompt\n",
    "system_prompt = \"You are a the engineering teacher. Anwswer the users breifly in 1-3 paragraphs. Do not go in too much details. Assume user has some prior knowledge of engineering concepts.\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "03e613f4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Diagnostic: environment, OpenAI package and Ollama checks. This is just to make sure all is setup properly.\n",
    "import requests\n",
    "import json\n",
    "import sys\n",
    "import traceback\n",
    "\n",
    "import openai  # Only for version check, does not redefine the client  # noqa: F811\n",
    "\n",
    "print(\"openai version:\", getattr(openai, '__version__', None))\n",
    "print(\"python:\", sys.version.splitlines()[0])\n",
    "print('\\nChecking Ollama /v1/models endpoint...')\n",
    "try:\n",
    "    r = requests.get('http://localhost:11434/v1/models', headers={'Authorization':'Bearer ollama'}, timeout=5)\n",
    "    print('models status:', r.status_code)\n",
    "    try:\n",
    "        print(json.dumps(r.json(), indent=2))\n",
    "    except Exception:\n",
    "        print(r.text)\n",
    "except Exception as e:\n",
    "    print('Could not reach Ollama models endpoint:', e)\n",
    "\n",
    "print('\\nTrying a non-streaming chat completion via the OpenAI client (ollama)...')\n",
    "try:\n",
    "    resp = ollama.chat.completions.create(\n",
    "        model=MODEL_LLAMA,\n",
    "        messages=[{\"role\":\"system\",\"content\":\"You are a test.\"},{\"role\":\"user\",\"content\":\"Say hi\"}],\n",
    "        stream=False,\n",
    "    )\n",
    "    try:\n",
    "        content = resp.choices[0].message.content\n",
    "    except Exception:\n",
    "        try:\n",
    "            content = resp.get('choices',[{}])[0].get('message',{}).get('content')\n",
    "        except Exception:\n",
    "            content = str(resp)\n",
    "    print('Assistant reply:', content)\n",
    "except Exception:\n",
    "    traceback.print_exc()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ad20e294",
   "metadata": {},
   "outputs": [],
   "source": [
    "def ask_llm(model_client, user_prompt, model, stream=False):\n",
    "    message = [{\"role\": \"system\", \"content\": system_prompt}, {\"role\": \"user\", \"content\": user_prompt}]\n",
    "\n",
    "    try:\n",
    "        response = model_client.chat.completions.create(\n",
    "            model=model,\n",
    "            messages=message,\n",
    "            stream=stream,\n",
    "        )\n",
    "    except Exception as e:\n",
    "        import traceback\n",
    "        print(\"Request failed:\", e)\n",
    "        traceback.print_exc()\n",
    "        return\n",
    "    display(Markdown(\"Model: \" + model))\n",
    "    display(Markdown(\"UserPrompt: \" + user_prompt))\n",
    "    if stream:\n",
    "        streaming = \"\"\n",
    "        display_handle = display(Markdown(\"\"), display_id=True)\n",
    "        for chunk in response:\n",
    "            streaming += chunk.choices[0].delta.content or ''\n",
    "            streaming = streaming.replace(\"```\",\"\").replace(\"markdown\", \"\")\n",
    "            update_display(Markdown(streaming), display_id=display_handle.display_id)\n",
    "\n",
    "    else:\n",
    "        return display(Markdown(response.choices[0].message.content))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3f0d0137-52b0-47a8-81a8-11a90a010798",
   "metadata": {},
   "outputs": [],
   "source": [
    "#Ask user to provide question.\n",
    "question = input(\"Please enter your engineering question: \");"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "60ce7000-a4a5-4cce-a261-e75ef45063b4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Get gpt-4o-mini to answer, with streaming\n",
    "ask_llm(openai, question, MODEL_GPT, stream=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f7c8ea8-4082-4ad0-8751-3301adcf6538",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Get Llama 3.2 to answer\n",
    "ask_llm(ollama, question, MODEL_LLAMA, stream=True)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "llm-engineering",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
